#!/usr/bin/env python
# -*- coding: utf-8 -*-


import random
import time
import datetime
import logging
_logger = logging.getLogger(__name__)

CMD_Wakeup = 0x0008
CMD_ReadDevice = 0x0001
CMD_SendReady = 0x0100
CMD_SendShutDown = 0x8000
CMD_RxdReady = 0x0300

CMD_SendNet = 0x0200


def to_hex_12_code(code):
    first_hex = 0x1000000000000
    return hex(first_hex + code)[3:]


# 这是 模拟下位机 生成数据用到的
class Sensor(object):
    def __init__(self, sensor_type, sensor_code, value=None):
        self.type = sensor_type
        self.code = sensor_code
        self.value = value
        if value == None:
            self.value = random.randint(10, 10000)

    @property
    def data(self):
        return {
            'code': self.code,
            'type': self.type,
            'value': self.value,
        }

# 这是 模拟下位机 生成数据用到的


class DeviceCheck(object):
    def __init__(self, ss_codes, check_time=None, battery=None):
        self.time = check_time
        self.battery = battery
        self.sensors = [Sensor(1, code).data for code in ss_codes]

        if check_time == None:
            # now = datetime.datetime.utcnow().timestamp()
            now = time.time()
            # print('slave time', now)
            now2 = round(now) - random.randint(0, 10000)
            self.time = now2

        if battery == None:
            self.battery = random.randint(10, 100)

    @property
    def data(self):
        return {
            # 'device_code': self.code,
            'time': self.time,
            'battery': self.battery,
            'sensors': self.sensors
        }


class Command(object):

    # 命令字, 子类继承后, 会重置这个值
    Cmd_word = 0x0000
    def __new__(cls, host, direction, hex_bytes=None, *args, **kwargs):

        if cls.check_hex_bytes(hex_bytes):
            # print('new, ', host, direction)
            return object.__new__(cls)
        else:
            # print('new, error', host, direction)
            return None

    def __init__(self, host, direction, hex_bytes=None, *args, **kwargs):
        # 参数
        # host 主机 or 从机
        # direction 下行 or 上行
        # hex_bytes 接收数据时, 收到数据list

        # print('Command, init, 1, hex_bytes', hex_bytes)

        self.host = host
        self.direction = direction
        self.hex_bytes = hex_bytes

        # print('Command, init, 1, hex_bytes', self.hex_bytes)
        if hex_bytes == None:
            self.hex_bytes = []

        # print('Command, init, 1, hex_bytes', self.hex_bytes)

        # self.data = self.get_data()

    @classmethod
    def check_hex_bytes(cls,  hex_bytes):
        return True

    def bytes2int(self, hex_bytes):
        # list 转 int

        value = 0
        for hex in hex_bytes:
            value = value * 256 + hex
        return value

    def int2bytes(self, num, byte_len):
        # int 转 list
        res = []
        old = num
        for index in range(0, byte_len):
            res = [old % 256] + res
            old = old // 256

        return res

    def next(self, ctrl):
        print()
        return None

    def ack(self, *args, **kwargs):
        return None


# ('host', 'down', odoo_status=odoo_status)

class CmdSendNet(Command):
    Cmd_word = CMD_SendNet
    Cmd_word_string = 'CMD_SendNet'

    def __init__(self, host, direction, hex_bytes=None, odoo_status=None):
        # 参数 code
        # txd 命令时 , 需要传参数 code
        # rxd 命令时 , 需要传参数 hex_bytes
        super(CmdSendNet, self).__init__(host, direction, hex_bytes=hex_bytes)
        self.odoo_status = odoo_status

    @property
    def data(self):
        # 根据 host 和 direction 不同, 分别计算 data, 计算依据 self.code 或 self.hex_bytes
        # print( 'get data: ', self.host, self.direction )
        if self.direction == 'down':
            # 主机 发送
            if self.host == 'host':
                # print('hsososs,', self.code)
                return self.Cmd_word, self.int2bytes(self.odoo_status, 4)


class CmdWakeup(Command):
    Cmd_word = CMD_Wakeup
    Cmd_word_string = 'CMD_Wakeup'

    def __init__(self, host, direction, hex_bytes=None):
        # 参数 code
        # txd 命令时 , 需要传参数 code
        # rxd 命令时 , 需要传参数 hex_bytes
        super(CmdWakeup, self).__init__(host, direction, hex_bytes=hex_bytes)

    @property
    def data(self):
        # 根据 host 和 direction 不同, 分别计算 data, 计算依据 self.code 或 self.hex_bytes
        # print( 'get data: ', self.host, self.direction )
        if self.direction == 'down':
            # 主机 发送
            if self.host == 'host':
                # print('hsososs,', self.code)
                return self.Cmd_word, []


class CmdReadDevice(Command):
    # 读取 device 的 传感器采样结果

    Cmd_word = CMD_ReadDevice
    Cmd_word_string = 'read device'

    devices = []
    rxd_data = {}

    def __init__(self, host, direction, hex_bytes=None, code=None, devices=None):
        # 参数 code
        # txd 命令时 , 需要传参数 code
        # rxd 命令时 , 需要传参数 hex_bytes
        super(CmdReadDevice, self).__init__(
            host, direction, hex_bytes=hex_bytes)
        self.code = code
        if devices != None:
            self.__class__.devices = devices

        if code:
            self.__class__.rxd_data[code]: None

        # self.__class__.rxd_data = {}

    @classmethod
    def check_hex_bytes(cls,  hex_bytes):
        # 下行 4字节 设备号
        return True

    @property
    def data(self):
        # 根据 host 和 direction 不同, 分别计算 data, 计算依据 self.code 或 self.hex_bytes
        # print( 'get data: ', self.host, self.direction )
        if self.direction == 'down':
            # 主机 发送
            if self.host == 'host':
                # print('hsososs,', self.code)
                to_send = self.int2bytes(self.code, 6)
                to_send = to_send + [0, 0, 0, 0]
                return self.Cmd_word, to_send

            # 从机 接收
            else:
                # print( '从机 接收数据 ',  )
                code = self.hex_bytes[0:6]
                mytime = self.hex_bytes[6:]

                return {
                    'cmd_word': self.Cmd_word,
                    'device_code': to_hex_12_code(self.bytes2int(code)),
                    'time': mytime
                    # hex(self.bytes2int(self.hex_bytes))[2:]
                }
        else:
            # 主机 接收
            if self.host == 'host':
                # print( '主机 接收数据 ' )
                # [
                #   0, 0, 0, 4,   code
                #   0xff, 1,  len
                #   95, 28, 194, 92, 时间
                #   1, 2, 22, 57,
                #   1, 1, 35, 87,
                #   0, 39, 电压
                # ]

                device_code = self.bytes2int(self.hex_bytes[0:6])
                # print('device_code ', device_code)
                # print('device_code ', hex(device_code))

                hex_bytes = self.hex_bytes[6:]

                dev_data = []

                while hex_bytes:
                    one_time = {}
                    hex_bytes = hex_bytes[1:]
                    if not hex_bytes:
                        break

                    ss_cnt = hex_bytes[0]
                    hex_bytes = hex_bytes[1:]
                    if len(hex_bytes) < 4 + ss_cnt * 2 + 2:
                        break

                    one_time['time'] = self.bytes2int(hex_bytes[0:4])
                    hex_bytes = hex_bytes[4:]
                    ss_data = []
                    for ss_index in range(0, ss_cnt):
                        ss_one = hex_bytes[0:4]
                        hex_bytes = hex_bytes[4:]

                        code = hex(ss_one[1])[2:]
                        if len(code) == 1:
                            code = '0' + code

                        ss_data.append({
                            'type': ss_one[0],
                            'code': code,
                            'value': self.bytes2int(ss_one[2:])
                        })

                    one_time['sensors'] = ss_data
                    one_time['battery'] = self.bytes2int(hex_bytes[0:2])
                    hex_bytes = hex_bytes[2:]
                    dev_data.append(one_time)

                # first_hex = 0x1000000000000
                return {
                    'dev_code': to_hex_12_code(device_code),
                    # hex(first_hex + device_code)[3:],
                    'dev_data': dev_data
                }

            # 从机 发送
            else:
                # print( '从机 发送数据 ',  )
                return {}

    def ack(self, sensors=None, *args, **kwargs):
        meta_sensors = sensors

        # 从机 应答
        if self.host != 'host':
            # mock 下位机程序, 回 设备检测结果
            _logger.info('rxd, ack, cmd=%s, data=%s' %
                         (self.Cmd_word_string, self.data))

            print('meta_sensors', meta_sensors)

            device_codes = [ss['device_code'] for ss in meta_sensors]

            device_codes = list(set(device_codes))
            device_codes = [int(dev, 16) for dev in device_codes]

            device_code = self.data.get('device_code')
            device_code = int(device_code, 16)
            if device_code:
                device_codes = [device_code]

            to_return = [self.Cmd_word]

            print(' device_codes,', device_code, device_codes)

            for dev in device_codes:
                # 所有的传感器

                # first_hex = 0x1000000000000

                sensors0 = [ss for ss in meta_sensors
                            if ss.get('device_code') == to_hex_12_code(dev)
                            # hex(first_hex+dev)[3:]
                            ]
                # print(' sensors0,', dev, sensors0)

                sensors0 = [int(ss['sensor_code'], 16) for ss in sensors0]
                sensors0 = list(set(sensors0))
                # print(' sensors0,2', dev, sensors0)

                # {cmd: 0x0001
                #  code: device_code
                #  data: [{time: 123123,
                #          battery: 12
                #          sensors: [{sensor_type: 1,
                #                     sensor_code: 1,
                #                     value: 0x1234}]}]}

                dev_data = [DeviceCheck(sensors0).data
                            for index1 in range(0, random.randint(1, 3))]

                to_send = {
                    'cmd_word': self.Cmd_word,
                    'dev_code': dev,
                    'dev_data': dev_data,
                }

                _logger.info('ack, %s' % to_send)
                wd, dt = self.quest_act_txd(to_send)
                to_return.append(dt)

            return to_return

        else:  # 上位机程序
            pass

            return None

        return None

    def quest_act_txd(self, to_send):
        # pass
        #     {cmd_word: 0x0001
        #      dev_code: device_code
        #      dev_data: [{time: 123123,
        #                  battery: 12
        #                  sensors: [{type: 1,
        #                             code: 1,
        #                             value: 0x1234}]}]}

        cmd_word = to_send.get('cmd_word')
        cmd_data = []
        dev_code = to_send.get('dev_code')
        # dev_code = int(dev_code, 16)
        cmd_data = cmd_data + self.int2bytes(dev_code, 6)
        for one_time in to_send.get('dev_data'):
            cmd_data = cmd_data + [0xff]
            sss = one_time.get('sensors')
            cmd_data = cmd_data + [len(sss)]
            cmd_data = cmd_data + self.int2bytes(one_time.get('time'), 4)

            for ss in sss:
                cmd_data = cmd_data + [ss.get('type')]
                cmd_data = cmd_data + [ss.get('code')]
                cmd_data = cmd_data + self.int2bytes(ss.get('value'), 2)

            cmd_data = cmd_data + self.int2bytes(one_time.get('battery'), 2)

        # print('quest_act_txd.', cmd_word, cmd_data)

        return cmd_word, cmd_data

        # txd_data(self, cmd_word, cmd_data):

    def next_format_data(self, data):
        # print(' next data,=', data)
        # _logger.info('next_format_data 1: %s' % data)

        device_code = data.get('dev_code')
        new_data = []

        dev_data = data.get('dev_data')
        # _logger.info('next_format_data 11: %s' % dev_data)
        for one_time in dev_data:
            timestamp = one_time.get('time')
            # max_int = 3248422333
            max_int = 2000000000
            if timestamp > max_int:
                timestamp = datetime.datetime(2000, 1, 1).timestamp()

            check_time = datetime.datetime.utcfromtimestamp(timestamp)
            check_time = check_time.strftime('%Y-%m-%d %H:%M:%S')

            # try:
            #     check_time = datetime.datetime.utcfromtimestamp(timestamp)
            #     check_time = check_time.strftime('%Y-%m-%d %H:%M:%S')
            # except Exception as e:
            #     check_time = '2100-01-01 00:00:00'

            etl_timestamp = timestamp

            # 设备电压 暂时未处理
            # battery = data.get('battery')

            sensors = one_time.get('sensors')
            for ss in sensors:
                # type2 = ss.get('type')
                code = ss.get('code')
                value = ss.get('value')

                # print('device_code', device_code)
                # print('code', code)
                sensor_code = '%s-%s' % (device_code, code)

                new_data.append({
                    'code': sensor_code,
                    'value': value,
                    'time': check_time,
                    'etl_timestamp': etl_timestamp,
                })

        return new_data

    def next(self, ctrl):
        odoo = ctrl.odoo
        db = ctrl.db

        # rxd 收到数据后的 后续处理
        # print(' next ', self.host, self.direction, self.data)

        if self.host == 'host':
            # 主机 收到 数据, 本地存储, 发送服务端
            # self.__class__.rxd_data = self.data

            devices = self.devices

            # first_hex = 0x1000000000000

            devices = [to_hex_12_code(dev) for dev in devices]

            # print('next, check devices,', devices)
            dev_code = self.data.get('dev_code')
            # print('next, check dev_code,', dev_code)

            if dev_code not in devices:
                _logger.info('dev=%s not in my list=%s.' %
                             (dev_code, devices))
                return

            _logger.info('Read dev=%s ok' % (dev_code))

            # print('not db?', db)
            # 存本地库
            # if not db:
            #     # print('not db')
            #     _logger.info('local DB not set. no save %s' % db)

            #     self.__class__.rxd_data[dev_code] = self.data

            #     return
            # else:
            #     _logger.info('local DB is set. to save %s' % db)

            # 格式化数据, 便于 数据存储
            # _logger.info('Read dev data: %s' % self.data)
            data = self.next_format_data(self.data)
            _logger.info('local save: %s' % data)

            LocalModel = db.env['iot.value']
            ids = LocalModel.update_or_create(data)
            _logger.info('local save count=%s' % len(ids))

            # 本地库所有数据 同步到 odoo, 并删除本地
            sensor_loc_to_odoo(odoo, db)
            self.__class__.rxd_data[int(dev_code, 16)] = self.data

            pass
        else:
            # 从机 收到 数据
            pass

        return None


class CmdSendReady(Command):
    # 发送 读取 gateway code

    Cmd_word = CMD_SendReady
    Cmd_word_string = 'Send Ready'

    Gateway_code = '000000000001'
    # Gateway_code = '0'

    rxd_gatewate_code = ''

    def __init__(self, host, direction, hex_bytes=None, now_timestamp=None):
        # 参数 code
        # txd 命令时 , 需要传参数 code
        # rxd 命令时 , 需要传参数 hex_bytes

        # print('CmdSendReady, init,', host, direction, hex_bytes)
        super(CmdSendReady, self).__init__(
            host, direction, hex_bytes=hex_bytes)
        self.now_timestamp = now_timestamp
        self.gateway_code = self.Gateway_code
        # print('CmdSendReady, init 2,', host, direction, self.hex_bytes)

    @property
    def data(self):
        # 根据 host 和 direction 不同, 分别计算 data, 计算依据 self.code 或 self.hex_bytes
        # print( 'get data: ', self.host, self.direction )
        if self.direction == 'down':
            # 主机 发送
            if self.host == 'host':
                return self.Cmd_word, self.int2bytes(self.now_timestamp, 4)

            # 从机 接收
            else:
                # print( '从机 接收数据 ',  )
                return {
                    'cmd_word': self.Cmd_word,
                    'device_code': self.bytes2int(self.hex_bytes)
                }
        else:
            # 主机 接收
            if self.host == 'host':
                # print( '主机 接收数据 ' )
                # print('主机 接收数据 1', self.hex_bytes)
                gatewate_code = self.bytes2int(self.hex_bytes)

                gatewate_code = to_hex_12_code(gatewate_code)

                # first_hex = 0x1000000000000

                # gatewate_code = hex(first_hex+gatewate_code)
                # gatewate_code = gatewate_code[3:]
                # print('主机 接收数据 2', gatewate_code)
                # print('主机 接收数据 3', gatewate_code)

                return {
                    'gatewate_code': gatewate_code,
                }

            # 从机 发送
            else:
                # print( '从机 发送数据 ',  )
                return {}

    def ack(self, *args, **kwargs):
        # 从机 应答
        if self.host != 'host':
            # mock 下位机程序, 回 设备号

            num = random.randint(0, 1)
            # if not num:
            #     _logger.info(
            #         'mock error ack, num=%s, slave no ack gateway code' % num)
            #     return None

            _logger.info('mock no error , ack, num=%s' % num)

            _logger.info('rxd, ack, cmd=%s, data=%s' %
                         (self.Cmd_word_string, self.data))

            to_send = {
                'cmd_word': self.Cmd_word,
                'gateway_code': self.gateway_code,
            }
            _logger.info('ack, %s' % to_send)
            return self.quest_act_txd(to_send)

    def quest_act_txd(self, to_send):
        # print('quest_act_txd.', to_send)
        cmd_word = to_send.get('cmd_word')

        gateway_code = to_send.get('gateway_code')
        print('quest_act_txd.gateway_code', gateway_code)
        gateway_code = int(gateway_code, 16)

        cmd_data = []
        cmd_data = cmd_data + self.int2bytes(gateway_code, 6)
        print('quest_act_txd.', cmd_word, cmd_data)
        return cmd_word, cmd_data

    def next(self, ctrl):
        # rxd 收到数据后的 后续处理
        gatewate_code = self.data.get('gatewate_code')
        _logger.info('++++ get gatewate_code= %s', gatewate_code)
        self.__class__.rxd_gatewate_code = gatewate_code


class CmdSendShutDown(Command):
    # 关机

    Cmd_word = CMD_SendShutDown
    Cmd_word_string = 'shut down'
    shutdown = 0

    def __init__(self, host, direction, hex_bytes=None, now_timestamp=None):
        # 参数 code
        # txd 命令时 , 需要传参数 code
        # rxd 命令时 , 需要传参数 hex_bytes

        # print('CmdSendReady, init,', host, direction, hex_bytes)
        super(CmdSendShutDown, self).__init__(
            host, direction, hex_bytes=hex_bytes)

        self.now_timestamp = now_timestamp

    @property
    def data(self):
        # 根据 host 和 direction 不同, 分别计算 data, 计算依据 self.code 或 self.hex_bytes
        # print( 'get data: ', self.host, self.direction )
        if self.direction == 'down':
            # 主机 发送
            if self.host == 'host':
                return self.Cmd_word, self.int2bytes(self.now_timestamp, 4)

            # 从机 接收
            else:
                # print( '从机 接收数据 ',  )
                pass
                return {
                    'cmd_word': self.Cmd_word,
                    'now_timestamp': self.bytes2int(self.hex_bytes)
                }

    def ack(self, *args, **kwargs):
        # 从机 应答
        if self.host != 'host':
            _logger.info('rxd, ack, cmd=%s, data=%s' %
                         (self.Cmd_word_string, self.data))

            to_send = {
                'cmd_word': self.Cmd_word,
                'timestamp': round(time.time()),
            }
            _logger.info('ack, %s' % to_send)
            return self.quest_act_txd(to_send)

    def quest_act_txd(self, to_send):
        # print('quest_act_txd.', to_send)
        cmd_word = to_send.get('cmd_word')
        timestamp = to_send.get('timestamp')
        cmd_data = []
        cmd_data = cmd_data + self.int2bytes(timestamp, 4)
        print('quest_act_txd.', cmd_word, cmd_data)
        return cmd_word, cmd_data

    def next(self, ctrl):
        # rxd 收到数据后的 后续处理
        _logger.info('+++ wait shutdown ok')
        self.__class__.shutdown = 1


class CmdRxdReady(Command):
    # 从机上行 呼叫 测试主机

    Cmd_word = CMD_RxdReady
    Cmd_word_string = 'CMD_RxdReady'

    def __init__(self, host, direction, hex_bytes=None,):
        # 参数 code
        # txd 命令时 , 需要传参数 code
        # rxd 命令时 , 需要传参数 hex_bytes

        # print('CmdSendReady, init,', host, direction, hex_bytes)
        super(CmdRxdReady, self).__init__(
            host, direction, hex_bytes=hex_bytes)

    @property
    def data(self):
        # 根据 host 和 direction 不同, 分别计算 data, 计算依据 self.code 或 self.hex_bytes
        # print( 'get data: ', self.host, self.direction )
        if self.direction == 'down':
            # 主机 发送
            if self.host == 'host':
                return self.Cmd_word, []

            # 从机 接收
            else:
                # print( '从机 接收数据 ',  )
                pass
                return {}
        else:
            # 主机 接收
            if self.host == 'host':
                # print( '主机 接收数据 ' )
                return {
                    # 'cmd_word': self.Cmd_word,
                    'now_timestamp': self.bytes2int(self.hex_bytes)
                }

            # 从机 发送
            else:
                # print( '从机 发送数据 ',  )
                return {}

    def ack(self, *args, **kwargs):

        # 从机 应答
        if self.host != 'host':
            # mock 下位机程序, 回 设备检测结果
            pass

        # 上位机程序
        else:
            # 上位机 应答

            # _logger.info('rxd, ack, cmd=%s, data=%s' %
            #              (self.Cmd_word_string, self.hex_bytes))

            to_send = {
                'cmd_word': self.Cmd_word,
                'timestamp': self.hex_bytes
            }
            # _logger.info('ack, %s' % to_send)
            return self.quest_act_txd(to_send)

        return None

    def quest_act_txd(self, to_send):
        # print('quest_act_txd.', to_send)
        cmd_word = to_send.get('cmd_word')
        timestamp = to_send.get('timestamp')
        cmd_data = []
        cmd_data = cmd_data + timestamp
        # print('quest_act_txd.', cmd_word, cmd_data)
        return cmd_word, cmd_data


commands = {
    CMD_Wakeup: CmdWakeup,
    CMD_ReadDevice: CmdReadDevice,
    CMD_SendReady: CmdSendReady,
    CMD_SendShutDown: CmdSendShutDown,
    CMD_RxdReady: CmdRxdReady


}

# 对外接口, 根据命令字 生成类


def get_commond(host, cmd_word, hex_bytes):
    Cls = commands.get(cmd_word)
    if not Cls:
        return None

    host2deriction = {
        'host': 'up',
        'guest': 'down'
    }

    return Cls(host, host2deriction[host], hex_bytes)


# 读取本地缓存数据, 上传到服务器, 若成功则本地删除
def sensor_loc_to_odoo(odoo, db):

    if not db:
        return

    Model = db.env['iot.value']
    loc_res = Model.search()
    _logger.info('local have %s records to save to server' % len(loc_res))

    if odoo.session_id and loc_res:
        LocalModel = db.env['iot.value']

        Value = odoo.env['iot.value']
        for rec in loc_res:
            new_rec = {}
            new_rec.update(rec)
            loc_id = new_rec['id']
            del new_rec['id']

            res = Value.update_or_create(new_rec)

            if res:
                # del loc id
                LocalModel.unlink(loc_id)

        _logger.info('send to server %s records' % len(loc_res))

    else:
        if not odoo.session_id:
            _logger.warning('dont connect server')
